package com.coder4j.starter.web.schedule;

import com.coder4j.starter.tools.util.UUIDUtils;
import com.coder4j.starter.web.config.RedisKeyConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public abstract class BaseHandler<T> {
    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private RedisKeyConfig redisKeyConfig;

    /**
     * 执行器唯一ID
     * 在整个项目运行过程中不可变，唯一标示一个执行器
     *
     * @return
     */
    public static final String handleUniqueId = UUIDUtils.getUUID();

    public String threadName() {
        Type genType = this.getClass().getGenericSuperclass();
        if(genType instanceof ParameterizedType){
            Type[] params = ((ParameterizedType)genType).getActualTypeArguments();
            Class<T> clazz = (Class<T>) params[0];
            return clazz.getSimpleName();
        }
        return Thread.currentThread().getName();
    }

    /**
     * 注意！！！ 这个方法是获取当前线程执行的方法名，定制的，不能乱用
     * @return
     */
    public abstract String handlerName();
    /**
     * 获取重入锁
     * 当定时任务在集群中需要单实例执行时，需要获取重入锁
     * 锁的时间定为77分钟，尽量避开周期为 5、10、15、30、60 分钟的定时任务
     *
     * @return 成功 true,失败 false
     */
    public boolean getLock() {
        String handlerName = handlerName();

        String label = "【" +handlerName + "】【" + handleUniqueId + "】【"  + threadName() + "】";

        //设置独占锁
        Boolean executeFlag = redisTemplate.opsForValue()
                .setIfAbsent(redisKeyConfig.scheduleLockPrefix + handlerName, handleUniqueId, 77L, TimeUnit.MINUTES);
        //加锁成功
        if (executeFlag != null && executeFlag) {
            log.info("获得了独占锁,当前线程: {}",label);
            return true;
        }
        //加锁失败，看看是不是自己加的锁
        Object object = redisTemplate.opsForValue().get(redisKeyConfig.scheduleLockPrefix + handlerName);
        String value = object == null ? null : (String) object;
        //是自己加的锁，重置有效期
        if (handleUniqueId.equals(value)) {
            redisTemplate.expire(redisKeyConfig.scheduleLockPrefix + handlerName, 77L, TimeUnit.MINUTES);
            log.info("获得了重入锁,当前线程: {}",label);
            return true;
        }
        //不是自己加的锁，失败
        log.warn("锁被其他线程占用,当前线程: {}，locked by {}", label, value);
        return false;
    }

}
